import hashlib
import logging
import os
import re
import subprocess
import sys
from abc import ABC, abstractmethod
from .utils import Queue, Stack

import config

# Import from parent directory
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class FunctionParser(object):
    WIN_FUNCPARSER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "FuncParser-opt.exe ")
    JAR_FUNCPARSER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "FuncParser-opt.jar ")
    DELIMITER = "\r\0?\r?\0\r"

    def __generate_cmd(self) -> str:
        if config.os_is_windows() and os.path.exists(self.WIN_FUNCPARSER):
            return self.WIN_FUNCPARSER
        else:
            return '%s -Xmx1024m -jar %s ' % (config.JAVA_BIN, self.JAR_FUNCPARSER)

    def __init__(self, encoding='utf-8'):
        self._cmd = self.__generate_cmd()
        self._encoding = encoding

    def parse(self, filepath, shallow=True) -> list:
        functions = []
        cmd = '%s "%s" %s' % (self._cmd, filepath, 0 if shallow else 1)
        try:
            ast_contents = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True).decode(self._encoding)
        except Exception as e:
            logging.exception("Parser Error:", e)
            return []

        ast_func_list = ast_contents.split(self.DELIMITER)

        def handle(li: list) -> list:
            ret = []
            for _ in li:
                if _:
                    ret.append(_)
            return sorted(set(ret), key=lambda _: len(_), reverse=True)

        for ast_func in ast_func_list[1:]:
            func = Function(filepath)
            elems = ast_func.split('\n')[1:-1]
            if len(elems) > 9:
                func.parentNumLoc = int(elems[1])
                func.name = elems[2]
                func.lines = (int(elems[3].split('\t')[0]), int(elems[3].split('\t')[1]))
                func.funcId = int(elems[4])
                if not shallow:
                    func.parameterList = handle(elems[5].rstrip().split('\t'))
                    func.variableList = handle(elems[6].rstrip().split('\t'))
                    func.dataTypeList = handle(elems[7].rstrip().split('\t'))
                    func.funcCalleeList = handle(elems[8].rstrip().split('\t'))
                func.funcBody = '\n'.join(elems[9:])
                functions.append(func)
        return functions


DEFAULT_FUNC_PARSER = FunctionParser()


class Serializer(ABC):
    @abstractmethod
    def serialize(self, o):
        pass


class Deserializer(ABC):
    @abstractmethod
    def deserialize(self, s):
        pass


class Sedes(Serializer, Deserializer, ABC):
    pass


class FuncFeatures(ABC):
    """用于封装对于Function进行handle后的返回数据"""

    @abstractmethod
    def get_all_feature_names(self):
        pass

    @abstractmethod
    def get_feature(self, feature_name: str):
        pass

    @abstractmethod
    def set_feature(self, feature_name: str, feature):
        pass

    @abstractmethod
    def get_sedes(self) -> Sedes:
        pass

    @staticmethod
    def get_deserializer(*args, **kwargs) -> Deserializer:
        return _FuncFeatures.SEDES


class Function(object):

    def __init__(self, file_name):
        self.parentFile = file_name  # Absolute file which has the function
        self.parentNumLoc = None  # Number of LoC of the parent file
        self.name = None  # Name of the function
        self.lines = None  # Tuple (lineFrom, lineTo) that indicates the LoC of function
        self.funcId = None  # n, indicating n-th function in the file
        self.parameterList = []  # list of parameter variables
        self.variableList = []  # list of local variables
        self.dataTypeList = []  # list of data types, including user-defined types
        self.funcCalleeList = []  # list of called functions' names
        self.funcBody = None

    def get_original_func_content(self):
        # returns the original function back from the instance.
        fp = open(self.parentFile, 'r')
        srcFileRaw = fp.readlines()
        fp.close()
        return ''.join(srcFileRaw[self.lines[0] - 1:self.lines[1]])

    def copy(self):
        func = Function(self.parentFile)
        func.parentNumLoc = self.parentNumLoc  # Number of LoC of the parent file
        func.name = self.name  # Name of the function
        func.lines = self.lines  # Tuple (lineFrom, lineTo) that indicates the LoC of function
        func.funcId = self.funcId  # n, indicating n-th function in the file
        func.parameterList = list(self.parameterList)  # list of parameter variables
        func.variableList = list(self.variableList)  # list of local variables
        func.dataTypeList = list(self.dataTypeList)  # list of data types, including user-defined types
        func.funcCalleeList = list(self.funcCalleeList)  # list of called functions' names
        func.funcBody = self.funcBody
        return func

    def get_func_body_hash(self) -> str:
        return hashlib.md5(self.funcBody.encode(config.ENCODING)).hexdigest()


class _FuncFeatures(FuncFeatures):
    ALL_FEATURE_NAMES = [
        'vuln_info', 'repo_name', 'fixed_commit_id', 'base_filename', 'func_name', 'abs_func_body_hash', 'abs_func_body', 'func_LoC',
        'func_id', 'param_num', 'lvar_num', 'dtype_num', 'funccall_num', 'if_num', 'else_num',
        'for_num', 'while_num', 'goto_num', 'switch_num', 'case_num', 'return_num', 'changes_info'
    ]

    ABSTRACT_PATTERNS = [
        ('if_num', re.compile('[^a-zA-Z0-9_$]if[^a-zA-Z0-9_$]', re.M)),
        ('else_num', re.compile('[^a-zA-Z0-9_$]else[^a-zA-Z0-9_$]', re.M)),
        ('for_num', re.compile('[^a-zA-Z0-9_$]for[^a-zA-Z0-9_$]', re.M)),
        ('while_num', re.compile('[^a-zA-Z0-9_$]while[^a-zA-Z0-9_$]', re.M)),
        ('goto_num', re.compile('[^a-zA-Z0-9_$]goto[^a-zA-Z0-9_$]', re.M)),
        ('switch_num', re.compile('[^a-zA-Z0-9_$]switch[^a-zA-Z0-9_$]', re.M)),
        ('case_num', re.compile('[^a-zA-Z0-9_$]case[^a-zA-Z0-9_$]', re.M)),
        ('return_num', re.compile('[^a-zA-Z0-9_$]return[^a-zA-Z0-9_$]', re.M)),
    ]

    INT_SET = [tup[0] for tup in ABSTRACT_PATTERNS]
    INT_SET.extend(['func_LoC', 'func_id', 'param_num', 'lvar_num', 'dtype_num', 'funccall_num'])

    class _Sedes(Sedes):
        SPLIT_TOKEN = '\n'
        DELIMITER_TOKEN = '='

        def serialize(self, o: FuncFeatures):
            keys = o.get_all_feature_names()
            return '{}'.format(self.SPLIT_TOKEN).join(
                ['%s%s%s' % (key, self.DELIMITER_TOKEN, o.get_feature(key)) for key in keys])

        def deserialize(self, s: str) -> FuncFeatures:
            s = s.strip()
            d = _FuncFeatures()
            for _ in s.split(self.SPLIT_TOKEN):
                pos = _.find(self.DELIMITER_TOKEN)
                if pos < 0:
                    raise ValueError('Invalid formatted content.')
                feature_name_ = _[:pos]
                feature_ = _[pos + 1:]
                d.set_feature(feature_name_, feature_)
            return d

    SEDES = _Sedes()

    def __init__(self):
        self._dict = {}

    def get_all_feature_names(self):
        return self._dict.keys()

    def get_feature(self, feature_name_: str):
        if feature_name_ not in self._dict.keys():
            return None
        return self._dict[feature_name_]

    def set_feature(self, feature_name_: str, feature_):
        if feature_name_ not in self.ALL_FEATURE_NAMES:
            raise KeyError('An unsupported feature_name named %s.' % feature_name_)
        self._dict[feature_name_] = int(feature_) if feature_name_ in self.INT_SET else feature_

    def get_sedes(self) -> Sedes:
        return self.SEDES


def extract_features(func: Function, lv: int, **kwargs) -> FuncFeatures:
    """
    Returns vuln_info, repo_name, fixed_commit_id, base_filename, func_name, abs_func_body_hash,
    abs_func_body, func_LoC, func_id, param_num, lvar_num, dtype_num,
    funccall_num, if_num, else_num, for_num, while_num, goto_num, switch_num, case_num, return_num, changes_info
    """
    abs_func = abstract(func, lv)
    abs_func_normalized = _SAMPLE_Normalizer.handle(abs_func)

    ret = _FuncFeatures()
    ret.set_feature('vuln_info', kwargs.get('vuln_info', ''))
    ret.set_feature('changes_info', kwargs.get('changes_info', ''))
    ret.set_feature('repo_name', kwargs.get('repo_name', ''))
    ret.set_feature('fixed_commit_id', kwargs.get('fixed_commit_id', ''))
    ret.set_feature('base_filename', os.path.basename(abs_func_normalized.parentFile))
    ret.set_feature('func_name', abs_func_normalized.name)
    ret.set_feature('abs_func_body_hash', abs_func_normalized.get_func_body_hash())
    ret.set_feature('abs_func_body', abs_func_normalized.funcBody)
    ret.set_feature('func_LoC', abs_func_normalized.lines[1] - abs_func_normalized.lines[0] + 1)
    ret.set_feature('func_id', abs_func_normalized.funcId)
    ret.set_feature('param_num', len(abs_func_normalized.parameterList))
    ret.set_feature('lvar_num', len(abs_func_normalized.variableList))
    ret.set_feature('dtype_num', len(abs_func_normalized.dataTypeList))
    ret.set_feature('funccall_num', len(abs_func_normalized.funcCalleeList))
    tmp_func_body = ' %s ' % abs_func_normalized.funcBody
    for feature_name, pattern in _FuncFeatures.ABSTRACT_PATTERNS:
        feature = len(pattern.findall(tmp_func_body))
        ret.set_feature(feature_name, feature)
    return ret


def _load(path: str, filename_cond, filesize_cond) -> list:
    walks = os.walk(path)
    ret = []
    for path, dirs, files in walks:
        for filename in files:
            if filename_cond(filename):
                filename_abspath = path.replace('\\', '/') + '/' + filename
                if filesize_cond(filename_abspath):
                    ret.append(filename_abspath)
    return ret


def filename_ends_in(s: str) -> bool:
    for suffix in config.VALID_SOURCE_SUFFICES:
        if s.endswith(suffix):
            return True
    return False


def load_source_files(path: str) -> list:
    """获取rootDir这个目录下的所有满足指定文件大小的所有文件全路径列表"""

    # returns the list of .src files under the specified root directory.
    def filename_cond(filename: str) -> bool:
        return filename_ends_in(filename)

    def filesize_cond(file_abspath: str) -> bool:
        return os.path.getsize(file_abspath) < config.MAX_FILE_SIZE

    return _load(path, filename_cond, filesize_cond)


def load_vul_files(path: str) -> list:
    # returns the list of .vul files under the specified root directory.
    # returns the list of .src files under the specified root directory.
    def filename_cond(filename: str) -> bool:
        return filename.endswith('OLD.vul')

    def filesize_cond(file_abspath: str) -> bool:
        return os.path.getsize(file_abspath) < config.MAX_FILE_SIZE

    return _load(path, filename_cond, filesize_cond)


def load_abs_feature_files(path: str) -> list:
    # returns the list of .vul files under the specified root directory.
    # returns the list of .src files under the specified root directory.
    def filename_cond(filename: str) -> bool:
        return filename.endswith('.features')

    def filesize_cond(file_abspath: str) -> bool:
        return os.path.getsize(file_abspath) < config.MAX_FILE_SIZE

    return _load(path, filename_cond, filesize_cond)


def load_abs_change_files(path: str) -> list:
    # returns the list of .vul files under the specified root directory.
    # returns the list of .src files under the specified root directory.
    def filename_cond(filename: str) -> bool:
        return filename.endswith('.changes')

    def filesize_cond(file_abspath: str) -> bool:
        return os.path.getsize(file_abspath) < config.MAX_FILE_SIZE

    return _load(path, filename_cond, filesize_cond)


PATTERN_COMMENTS = re.compile(
    r'(?P<comment>//.*?$|[{}]+)|(?P<multilinecomment>/\*.*?\*/)|(?P<noncomment>\'(\\.|[^\\\'])*\'|"(\\.|[^\\"])*"|.[^/\'"]*)',
    re.DOTALL | re.MULTILINE)


def remove_comment(string):
    # Code for removing C/C++ style comments. (Imported from ReDeBug.)
    return ''.join([c.group('noncomment') for c in PATTERN_COMMENTS.finditer(string) if c.group('noncomment')])


# def getBody(originalFunction):
#   # returns the function's body as a string.
#   return originalFunction[originalFunction.find('{')+1:originalFunction.rfind('}')]


PATTERN_NORMALIZE = re.compile('\\s+', re.RegexFlag.MULTILINE)
SINGLE_CHAR_DEFINED_NAME_PATTERN = re.compile(
    '[a-zA-Z0-9_$]',
    re.RegexFlag.MULTILINE
)


def normalize(string):
    # Code for normalizing the input string.
    # LF and TAB literals, curly braces, and spaces are removed,
    # and all characters are lowercased.
    def replace_(group_):
        span = group_.span()
        l = string[span[0] - 1]
        r = string[span[1]]
        if SINGLE_CHAR_DEFINED_NAME_PATTERN.match(l) is not None and SINGLE_CHAR_DEFINED_NAME_PATTERN.match(
                r) is not None:
            return ' '
        else:
            return ''

    string = PATTERN_NORMALIZE.sub(' ', string.strip())
    return PATTERN_NORMALIZE.sub(replace_, string).lower()


class _Handler(ABC):
    @abstractmethod
    def handle(self, func: Function) -> Function:
        pass


class _DoNothing(_Handler):
    def handle(self, func: Function) -> Function:
        return func


_SAMPLE_DoNothing = _DoNothing()


class _AbstractHandler(_Handler):
    def __init__(self, handler: _Handler = _SAMPLE_DoNothing):
        self._handler = handler

    @abstractmethod
    def _process(self, func: Function) -> Function:
        pass

    def handle(self, func: Function) -> Function:
        func = self._handler.handle(func)
        return self._process(func)


class _NoteRemover(_AbstractHandler):

    def _process(self, func: Function) -> Function:
        func.funcBody = remove_comment(func.funcBody)
        return func


_SAMPLE_NoteRemover = _NoteRemover(handler=_SAMPLE_DoNothing)


class _Level0(_AbstractHandler):
    """Do nothing"""

    def __init__(self):
        super().__init__()

    def _process(self, func: Function) -> Function:
        return func.copy()


_SAMPLE_Level0 = _Level0()


class _Level1(_AbstractHandler):
    """Just removes notes"""

    def __init__(self):
        super().__init__(handler=_SAMPLE_Level0)

    def _process(self, func: Function) -> Function:
        return _SAMPLE_NoteRemover.handle(func)


_SAMPLE_Level1 = _Level1()


def _replace(content: str, pattern_str: str, replace_str: str) -> str:
    def replace_(group_):
        header = group_.group(1)
        rear = group_.group(2)
        return group_.group(0) if not header or header.strip() == '->' else header + replace_str + rear

    pattern = re.compile('(.[^a-zA-Z0-9_$\\.]\\s*)%s([^a-zA-Z0-9_$])' % pattern_str, re.RegexFlag.MULTILINE | re.DOTALL)
    # exec two replacements at least
    content_a = pattern.sub(replace_, content)
    content_b = pattern.sub(replace_, content_a)
    while content_a != content_b:
        content_a = pattern.sub(replace_, content_b)
        content_b = pattern.sub(replace_, content_a)
    return content_a


class _Level2(_AbstractHandler):
    """Removes notes and Replaces function params"""
    TAG = 'FPARAM'

    def __init__(self):
        super().__init__(handler=_SAMPLE_Level1)

    def _process(self, func: Function) -> Function:
        for param in func.parameterList:
            func.funcBody = _replace(func.funcBody, param, self.TAG)
        return func


_SAMPLE_Level2 = _Level2()


class _Level3(_AbstractHandler):
    """Removes notes, Replaces function params and Replaces function local variants."""
    TAG = 'LVAR'

    def __init__(self):
        super().__init__(handler=_SAMPLE_Level2)

    def _process(self, func: Function) -> Function:
        for param in func.variableList:
            func.funcBody = _replace(func.funcBody, param, self.TAG)
        return func


_SAMPLE_Level3 = _Level3()


class _Level4(_AbstractHandler):
    """
    Removes notes, Replaces function params, Replaces function local variants
    and Replaces function data types.
    """
    TAG = 'DTYPE'

    def __init__(self):
        super().__init__(handler=_SAMPLE_Level3)

    def _process(self, func: Function) -> Function:
        for param in func.dataTypeList:
            func.funcBody = _replace(func.funcBody, param, self.TAG)
        return func


_SAMPLE_Level4 = _Level4()


class _Level5(_AbstractHandler):
    """
    Removes notes, Replaces function params, Replaces function local variants,
    Replaces function data type and Replaces inner function callees.
    """
    TAG = 'FUNCCALL'

    def __init__(self):
        super().__init__(handler=_SAMPLE_Level4)

    def _process(self, func: Function) -> Function:
        for param in func.funcCalleeList:
            func.funcBody = _replace(func.funcBody, param, self.TAG)
        return func


_SAMPLE_Level5 = _Level5()

__ABSTRACT_LEVEL_MAP = {
    0: _SAMPLE_Level0,
    1: _SAMPLE_Level1,
    2: _SAMPLE_Level2,
    3: _SAMPLE_Level3,
    4: _SAMPLE_Level4,
    5: _SAMPLE_Level5,
}


class Normalizer(_AbstractHandler):

    def _process(self, func: Function) -> Function:
        func.funcBody = normalize(func.funcBody)
        return func


_SAMPLE_Normalizer = Normalizer()
DEFAULT_NORMALIZER = _SAMPLE_Normalizer


def abstract(func: Function, level) -> Function:
    # Applies abstraction on the function instance,
    # and then returns a tuple consisting of the original body and abstracted body.
    handler = __ABSTRACT_LEVEL_MAP[level]
    return handler.handle(func)


class PatchDiffInfo(ABC):
    @abstractmethod
    def get_change_adds(self):
        pass

    @abstractmethod
    def get_change_dels(self):
        pass

    @abstractmethod
    def add_change_add(self, change):
        pass

    @abstractmethod
    def add_change_del(self, change):
        pass

    @abstractmethod
    def get_sedes(self) -> Sedes:
        pass

    @staticmethod
    def get_deserializer(*args, **kwargs) -> Deserializer:
        return _PatchDiffInfo.SEDES


class _PatchDiffInfo(PatchDiffInfo):
    PATTERN_CHUNK_WITHOUT_HEADER = re.compile(r'@@\s[^a-zA-Z]*\s[^a-zA-Z]*\s@@', re.M)
    ADD_TAG = 'ADD'
    DEL_TAG = 'DEL'

    def __init__(self):
        self._adds = set()
        self._dels = set()

    class _Sedes(Sedes):
        SPLIT_TOKEN = '\n69f3503e-daee-4427-be55-8375a0a167db\n'
        DELIMITER_TOKEN = '\n'

        def serialize(self, o: PatchDiffInfo) -> str:
            adds = o.get_change_adds()
            dels = o.get_change_dels()
            adds_info, dels_info = '', ''
            if len(adds) > 0:
                adds_info = self.DELIMITER_TOKEN.join((_PatchDiffInfo.ADD_TAG, self.DELIMITER_TOKEN.join(adds)))
            if len(dels) > 0:
                dels_info = self.DELIMITER_TOKEN.join((_PatchDiffInfo.DEL_TAG, self.DELIMITER_TOKEN.join(dels)))

            if adds_info:
                if dels_info:
                    return self.SPLIT_TOKEN.join((adds_info, dels_info))
                else:
                    return adds_info
            else:
                if dels_info:
                    return dels_info
                else:
                    return ''

        def deserialize(self, s) -> PatchDiffInfo:
            d = _PatchDiffInfo()

            s = s.strip()
            splits = s.split(self.SPLIT_TOKEN)
            len_ = len(splits)
            if len_ > 2 or len_ <= 0:
                raise ValueError('Invalid formatted data.')

            def fill(content: str):
                content = content.strip()
                changes = content.split(self.DELIMITER_TOKEN)
                tag = changes[0].strip()
                if tag == _PatchDiffInfo.ADD_TAG:
                    for change in changes[1:]:
                        change = change.strip()
                        d.add_change_add(change)
                elif tag == _PatchDiffInfo.DEL_TAG:
                    for change in changes[1:]:
                        change = change.strip()
                        d.add_change_del(change)
                else:
                    raise TypeError('An unsupported patch diff info tag named %s' % tag)

            fill(splits[0])
            if len_ == 2:
                fill(splits[1])
            return d

    SEDES = _Sedes()

    def get_change_adds(self):
        return self._adds

    def get_change_dels(self):
        return self._dels

    def add_change_add(self, change):
        self._adds.add(change)

    def add_change_del(self, change):
        self._dels.add(change)

    def get_sedes(self) -> Sedes:
        return self.SEDES


def extract_patch_diff_info(patch_file: str, func: Function, lv: int) -> PatchDiffInfo:
    with open(patch_file, 'r', encoding=config.ENCODING) as fp:
        patch_content = fp.read()
    ret = _PatchDiffInfo()
    for patch_info in _PatchDiffInfo.PATTERN_CHUNK_WITHOUT_HEADER.split(patch_content)[1:]:
        add_ = False
        del_ = False
        add_list = []
        adds_list = []
        del_list = []
        dels_list = []

        for line in patch_info.split('\n'):
            line = line.strip()
            if line.startswith('+'):
                if del_:
                    if del_list:
                        dels_list.append('\n'.join(del_list))
                    del_ = False
                if not add_:
                    add_ = True
                    add_list.clear()
                line = line.lstrip('+').strip()
                if not line:
                    continue
                add_list.append('  %s  ' % line)
            elif line.startswith('-'):
                if add_:
                    if add_list:
                        adds_list.append('\n'.join(add_list))
                    add_ = False
                if not del_:
                    del_ = True
                    del_list.clear()
                line = line.lstrip('-').strip()
                if not line:
                    continue
                del_list.append('  %s  ' % line)
            else:
                if add_:
                    if add_list:
                        adds_list.append('\n'.join(add_list))
                    add_ = False
                if del_:
                    if del_list:
                        dels_list.append('\n'.join(del_list))
                    del_ = False
        for change in adds_list:
            func.funcBody = change
            abs_func = abstract(func, lv)
            info = normalize(abs_func.funcBody)
            ret.add_change_add(info)
        for change in dels_list:
            func.funcBody = change
            abs_func = abstract(func, lv)
            info = normalize(abs_func.funcBody)
            ret.add_change_del(info)
    return ret


def union_function_list_params(*funcs) -> Function:
    ret = Function('')
    if len(funcs) == 0:
        return ret
    elif len(funcs) == 1:
        return funcs[0]

    def union(f1: Function, f2: Function):
        for _ in f2.parameterList:
            f1.parameterList.append(_)
        for _ in f2.variableList:
            f1.variableList.append(_)
        for _ in f2.dataTypeList:
            f1.dataTypeList.append(_)
        for _ in f2.funcCalleeList:
            f1.funcCalleeList.append(_)
        return f1

    for func in funcs:
        ret = union(ret, func)
    return ret


def handled_func_split(s: str) -> list:
    """将抽象正则化之后的函数进行split处理，split策略是根据语句以及代码块进行切割"""
    ret = []

    def _split(str_: str):
        try:
            str_ = str_.strip()
            first_colon_pos = str_.find(';')
            first_for_pos = str_.find('for(')
            if 0 <= first_for_pos < first_colon_pos:
                st = Stack()
                st.push('(')
                i = first_for_pos + 3
                while not st.empty():
                    i += 1
                    ch = str_[i]
                    if ch == '(':
                        st.push(ch)
                    elif ch == ')':
                        if st.peek() != '(':
                            raise ValueError('Parentheses not exists in both.')
                        st.pop()
                    else:
                        pass
                del st
                first_colon_pos = i + 1 + str_[i + 1:].find(';')
            first_left_brace_pos = str_.find('{')
            if first_colon_pos >= 0:    # ;出现
                if first_left_brace_pos >= 0:   # {出现
                    if first_colon_pos < first_left_brace_pos:  # ;出现在{的左边
                        token = str_[:first_colon_pos]  # 得到语句
                        ret.append(token)  # 添加语句
                        str_ = str_[first_colon_pos + 1:]   # 将源代码块去掉本语句
                        _split(str_)    # 对去掉后的执行split
                    else:   # {先于;出现
                        # 先提取{}语句块
                        st = Stack()
                        st.push('{')
                        i = first_left_brace_pos
                        while not st.empty():
                            i += 1
                            ch = str_[i]
                            if ch == '{':
                                st.push(ch)
                            elif ch == '}':
                                if st.peek() != '{':
                                    raise ValueError('Braces not exists in both.')
                                st.pop()
                            else:
                                pass
                        del st
                        # 此时_str[i]=='}'
                        braces_token = str_[:i + 1]     # 提取{}语句块
                        ret.append(braces_token)
                        _split(str_[first_left_brace_pos + 1: i])   # split{}语句块中的内容
                        _split(str_[i + 1:])  # split{}语句块后面的内容
                else:   # ;出现而{没有出现
                    token = str_[:first_colon_pos]  # 得到语句
                    ret.append(token)  # 添加语句
                    str_ = str_[first_colon_pos + 1:]  # 将源代码块去掉本语句
                    _split(str_)  # 对去掉后的执行split
            else:   # ;与{均未出现表明结束
                # do nothing
                pass
        except Exception as e:
            logging.exception(e)
            if str_:
                ret.append(str_)
    _split(s)
    return ret
